package com.gqb.spring.bean;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.annotation.Resource;

import org.apache.commons.digester.Digester;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.xml.sax.SAXException;

import com.gqb.spring.framework.InitializingBeanProcessor;
import com.gqb.spring.tag.Bean;
import com.gqb.spring.tag.Beans;
import com.gqb.spring.tag.Property;
import com.gqb.spring.tag.Scan;

public class BeanUtil {
	public static Map<String, Bean> beanMap = new HashMap<String, Bean>();
	public static List<Scan> scans = new ArrayList<Scan>();
	public static Map<String, Object> objMap = new HashMap<String, Object>();
	public static Map<String, Class> beanIdClazzMap = new HashMap<String, Class>();
	static {
		try {
			load();
			loadAnnotation();
		} catch (Exception e) {
			// TODO: handle exception
		}
	}

	/**
	 * 用digester 来解析xml
	 * 
	 * @throws NoSuchMethodException
	 * @throws SecurityException
	 * @throws IOException
	 * @throws SAXException
	 * @throws ClassNotFoundException
	 * @throws InstantiationException
	 * @throws IllegalAccessException
	 * @throws IllegalArgumentException
	 * @throws InvocationTargetException
	 */
	private static void load() throws NoSuchMethodException, SecurityException,
			IOException, SAXException, ClassNotFoundException,
			InstantiationException, IllegalAccessException,
			IllegalArgumentException, InvocationTargetException {
		Digester digester = new Digester();
		digester.setValidating(false);
		digester.addObjectCreate("beans", Beans.class);
		// 如果配置文件中有多个bean，add一次即可
		digester.addObjectCreate("beans/bean", Bean.class);
		digester.addObjectCreate("beans/scan", Scan.class);
		// 设置bean的属性<bean name="",id="">中的id和name。默认属性名和类中的属性名一样，不同的要特殊配置
		digester.addSetProperties("beans/bean", "class", "className");
		digester.addSetProperties("beans/bean","init-method","initMethodName");
		digester.addSetProperties("beans/bean", "destroy-method", "destroyMethodName");
		digester.addSetProperties("beans/bean");
		digester.addSetProperties("beans/scan", "package", "packageName");
		digester.addSetNestedProperties("beans/scan");
		digester.addObjectCreate("beans/bean/property", Property.class);
		// 将标签中的属性赋给对象
		digester.addSetProperties("beans/bean/property");
		// 设置对象间的关系
		digester.addSetNext("beans/bean/property", "addProperty");
		digester.addSetNext("beans/bean", "addBean");
		digester.addSetNext("beans/scan", "addScan");
		InputStream in = ClassLoader.getSystemResourceAsStream("beans.xml");
		//从根节点开始解析
		Beans beans = (Beans) digester.parse(in);
		List<Bean> beanList = beans.getBeans();
		scans.addAll(beans.getScans());
		for (Bean bean : beanList) {
			beanMap.put(bean.getId(), bean);
		}
		for (Bean bean : beanList) {
			//TODO 这里要解决 相互依赖然后都无法初始化的问题
			//这里先初始化BeanB
			if ("com.gqb.xml.bean.BeanB".equals(bean.getClassName())) {
				Class clazz = Class.forName(bean.getClassName());
				Object object = clazz.newInstance();
				List<Property> props = bean.getProps();
				String methodName = "set"
						+ StringUtil.bigCaseFirstChar(props.get(0).getName());
				Method m = clazz.getMethod(methodName, String.class);
				m.invoke(object, props.get(0).getValue());
				BeanB beanB = (BeanB) object;
				// System.out.println(beanB.getTitle());
				objMap.put(bean.getId(), beanB);
				break;
			}
		}
		in.close();
	}

	@SuppressWarnings({ "unchecked", "rawtypes" })
	private static void loadAnnotation() throws Exception {
		for (int i = 0; i < scans.size(); i++) {
			String packageName = scans.get(i).getPackageName();
			String rootPath = BeanUtil.class.getResource("/").getPath();
			rootPath = URLDecoder.decode(rootPath, "utf-8");
			File file = new File(rootPath + File.separator
					+ packageName.replace(".", File.separator));
			String[] fileNames = file.list();
			for (String fileName : fileNames) {
				String className = fileName.substring(0, fileName.length()
						- ".class".length());
				String fullClassName = packageName + "." + className;
				Class clazz = Class.forName(fullClassName);
				Component component = (Component) clazz
						.getAnnotation(Component.class);
				if (null != component) {
					String beanId = component.value();
					if (StringUtils.isEmpty(beanId)) {
						beanId = StringUtil.smallCaseFirstChar(className);
					}
					beanIdClazzMap.put(beanId, clazz);
				}
			}
		}
	}

	@SuppressWarnings({ "rawtypes", "unchecked" })
	public static Object getBean(String beanId) throws Exception {
		Object obj = objMap.get(beanId);
		if (null != obj) {
			return obj;
		}
		obj = getBeanFromXml(beanId);
		if (null != obj)
		{
			InitializingBeanProcessor.Process(obj, beanMap.get(beanId), beanIdClazzMap.get(beanId));
			return obj;
		}
		obj = getBeanFromAnnotations(beanId);
		InitializingBeanProcessor.Process(obj, beanMap.get(beanId), beanIdClazzMap.get(beanId));
		return obj;
	}

	@SuppressWarnings({ "unchecked", "rawtypes" })
	private static Object getBeanFromXml(String beanId) throws Exception {
		Object obj = objMap.get(beanId);
		if (null != obj) {
			return obj;
		}
		Bean bean = beanMap.get(beanId);
		if (null == bean)
			return obj;
		Class clazz = Class.forName(bean.getClassName());
		obj = clazz.newInstance();
		List<Property> props = bean.getProps();
		for (Property property : props) {
			String methodName = "set" + StringUtil.bigCaseFirstChar(property.getName());
			if (null != property.getValue()) {
				Method method = clazz.getDeclaredMethod(methodName,
						String.class);
				method.invoke(obj, property.getValue());
			}
			if (null != property.getRef()) {
				Bean childBean = beanMap.get(property.getRef());
				Class clazzChild;
				if (null != childBean) {
					clazzChild = Class.forName(childBean.getClassName());
				} else {
					clazzChild = beanIdClazzMap.get(property.getRef());
				}
				Method m = clazz.getMethod(methodName, clazzChild);
				Object objChild = getBean(property.getRef());
				m.invoke(obj, objChild);
			}
		}

		if (null != obj) {
			objMap.put(beanId, obj);
		}
		return obj;
	}

	@SuppressWarnings("unchecked")
	private static Object getBeanFromAnnotations(String beanId)
			throws Exception {
		Class clazz = beanIdClazzMap.get(beanId);
		if (null == clazz) {
			return null;
		}
		Object obj = clazz.newInstance();
		Field[] fields = clazz.getDeclaredFields();
		for (Field field : fields) {
			String methodName = "set" + StringUtil.bigCaseFirstChar(field.getName());
			Method method = clazz.getMethod(methodName, String.class);
			Value value = field.getAnnotation(Value.class);
			if (null != value) {
				String propertyValue = value.value();
				method.invoke(obj, propertyValue);
			}
			Resource resource = field.getAnnotation(Resource.class);
			if (null != resource) {
				String propertyBeanId = resource.name();
				if (StringUtils.isEmpty(propertyBeanId)) {
					propertyBeanId = StringUtil.smallCaseFirstChar(field.getType()
							.getSimpleName());
				}
				Object propertyObj = getBean(propertyBeanId);
				method.invoke(propertyObj, propertyObj);
			}
		}
		if (null != obj) {
			objMap.put(beanId, obj);
		}
		return obj;
	}


	public static String Seperator = "/";
}
